package com.hospital.hms.service.impl;

import cn.hutool.core.date.DateUtil;
import cn.hutool.core.map.MapUtil;
import com.hospital.hms.controller.form.DoctorScheduleSlotVO;
import com.hospital.hms.dao.DoctorWorkPlanDao;
import com.hospital.hms.dao.DoctorWorkPlanScheduleDao;
import com.hospital.hms.exception.GlobalException;
import com.hospital.hms.pojo.DoctorWorkPlanSchedule;
import com.hospital.hms.service.DoctorWorkPlanScheduleService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.util.*;

@Service
@Slf4j
public class DoctorWorkPlanScheduleServiceImpl implements DoctorWorkPlanScheduleService {

    @Autowired
    DoctorWorkPlanScheduleDao doctorWorkPlanScheduleDao;

    @Autowired
    RedisTemplate redisTemplate;

    @Autowired
    DoctorWorkPlanDao doctorWorkPlanDao;

    private String DOCTOR_SCHEDULE_KEY = "doctor_schedule_";

    /**
     * 定义一个不可变的时间段映射，表示医生出诊的时间段
     * 时段 1: 08:00
     * 时段 2: 08:30
     * ...
     */
    private static final Map<String, String> APPOINTMENT_SLOT = Map.ofEntries(
            new AbstractMap.SimpleEntry<>("1", "08:00"),
            new AbstractMap.SimpleEntry<>("2", "08:30"),
            new AbstractMap.SimpleEntry<>("3", "09:00"),
            new AbstractMap.SimpleEntry<>("4", "09:30"),
            new AbstractMap.SimpleEntry<>("5", "10:00"),
            new AbstractMap.SimpleEntry<>("6", "10:30"),
            new AbstractMap.SimpleEntry<>("7", "11:00"),
            new AbstractMap.SimpleEntry<>("8", "11:30"),
            new AbstractMap.SimpleEntry<>("9", "13:00"),
            new AbstractMap.SimpleEntry<>("10", "13:30"),
            new AbstractMap.SimpleEntry<>("11", "14:00"),
            new AbstractMap.SimpleEntry<>("12", "14:30"),
            new AbstractMap.SimpleEntry<>("13", "15:00"),
            new AbstractMap.SimpleEntry<>("14", "15:30"),
            new AbstractMap.SimpleEntry<>("15", "16:00")
    );

    @Transactional
    void insertScheduleHandle(ArrayList<DoctorWorkPlanSchedule> list){
        for (DoctorWorkPlanSchedule entity : list) {
            doctorWorkPlanScheduleDao.insert(entity);
        }
    }

    @Override
    public void insert(ArrayList<DoctorWorkPlanSchedule> list) {
        insertScheduleHandle(list);
        addScheduleCache(list);  //避免出现超售的情况
    }

    private void addScheduleCache(ArrayList<DoctorWorkPlanSchedule> list) {
        //如果list中没有元素，就不需要创建缓存
        if (list == null || list.size() == 0) {
            return;
        }
        //一个出诊计划中插入到schedule数据表的时间段记录的外键值相同，取出列表第一个元素获得外键
        int workPlanId = list.get(0).getWorkPlanId();
        //查询数据库记录
        ArrayList<HashMap> newList = doctorWorkPlanScheduleDao.selectScheduleByWorkPlanId(workPlanId);
        for (HashMap one : newList) {
            int id = MapUtil.getInt(one, "scheduleId");
            int slot = MapUtil.getInt(one, "slot");
            //定义缓存记录的Key，保存到redis的key
            String key = DOCTOR_SCHEDULE_KEY + id;
            //把出诊时间段缓存到Redis
            /*
              保存到redis为如下结构：
                doctor_schedule_100 scheduleId  100
                doctor_schedule_100 slot  1
            * */
            redisTemplate.opsForHash().putAll(key, one);
            //出诊日期, 例如：2024-04-23
            String date = MapUtil.getStr(one, "date");

            /*
             * 获取该时间段对应的起始时间，再根据slot的值获取到对应的时段
             * 例如： slot = 1 就会返回 08:00
             * */
            String time = APPOINTMENT_SLOT.get(String.valueOf(slot));

            /*
             * 设置缓存过期时间 2024-04-23 08:00 表示这条数据在指定的时间会被删除。
             * 如果患者在2024-04-23 08:00挂号的时候就不能挂2024-04-23 08:00这个时间的号了
             * 因为该时间已经开始接诊了。只能在2024-04-23 08:00时间之前该该时段的号。
             * */
            redisTemplate.expireAt(key, DateUtil.parse(date + " " + time));
        }
    }

    @Override
    public ArrayList selectDoctorScheduleByDeptSubIdAndDate(Map param) {
        // 获取查询结果
        /**
         * map => {
         *     "date": "2025-02-20",
         *     "deptSubId": 1
         * }
         * 结果如下
         * [{"workPlanId":117,"slot":1,"doctorName":"韩倩倩","doctorId":20,"maximum":3},
         * {"workPlanId":117,"slot":2,"doctorName":"韩倩倩","doctorId":20,"maximum":3},
         * {"workPlanId":117,"slot":3,"doctorName":"韩倩倩","doctorId":20,"maximum":3},
         * {"workPlanId":123,"slot":1,"doctorName":"赵六","doctorId":88,"maximum":2},
         * {"workPlanId":123,"slot":2,"doctorName":"赵六","doctorId":88,"maximum":2},
         * {"workPlanId":123,"slot":3,"doctorName":"赵六","doctorId":88,"maximum":2}]
         * */
        ArrayList<HashMap> list = doctorWorkPlanScheduleDao.selectDoctorScheduleByDeptSubIdAndDate(param);
//            String jsonStr = JSONUtil.toJsonStr(list);
//            System.out.println(jsonStr);

        // 结果映射表，Key: doctorId，Value: 该医生的 HashMap
        LinkedHashMap<Integer, HashMap> doctorMap = new LinkedHashMap<>();

        // 遍历查询结果并整理数据
        for (HashMap map : list) {
            int doctorId = MapUtil.getInt(map, "doctorId");
            int slot = MapUtil.getInt(map, "slot");

            // 获取医生记录
            HashMap doctor = doctorMap.computeIfAbsent(doctorId, k -> {
                HashMap newDoctor = new HashMap<>(map);
                newDoctor.put("slot", new ArrayList<Boolean>(Collections.nCopies(15, Boolean.FALSE)));
                return newDoctor;
            });

            // 更新 slot 时段
            ArrayList<Boolean> slotList = (ArrayList<Boolean>) doctor.get("slot");
            if (slot >= 1 && slot <= 15) {
                slotList.set(slot - 1, Boolean.TRUE);
            }
        }

        // 返回医生数据列表
        return new ArrayList<>(doctorMap.values());
    }

    @Override
    public HashMap selectScheduleByWorkPlanId(int workPlanId) {
        ArrayList<HashMap> list = doctorWorkPlanScheduleDao.selectScheduleByWorkPlanId(workPlanId);
        HashMap result = new HashMap();
        ArrayList temp = new ArrayList();
        for (int i = 0; i < list.size(); i++) {
            HashMap map = list.get(i);
            int scheduleId = MapUtil.getInt(map, "scheduleId");
            int doctorId = MapUtil.getInt(map, "doctorId");
            int maximum = MapUtil.getInt(map, "maximum");
            int num = MapUtil.getInt(map, "num");
            int slot = MapUtil.getInt(map, "slot");
            if (i == 0) {
                result.put("doctorId", doctorId);
                result.put("maximum", maximum);
            }
            temp.add(new HashMap<>() {{
                put("scheduleId", scheduleId);
                put("slot", slot);
                put("num", num);
            }});
        }
        result.put("slots", temp);
        return result;
    }

    @Override
    public void update(Map param) {
//        删除或添加时间段
        ArrayList<DoctorWorkPlanSchedule> addOrDeleteList = insertOrDeleteScheduleHandle(param);
//        把新添加的出诊时段缓存到redis
        addScheduleCache(addOrDeleteList);
    }

    @Transactional
    ArrayList<DoctorWorkPlanSchedule> insertOrDeleteScheduleHandle(Map param){
        //        更新接诊人数上限
        doctorWorkPlanScheduleDao.update(param);

        Integer workPlanId = MapUtil.getInt(param, "workPlanId");
        Integer maximum = MapUtil.getInt(param, "maximum");
        
//        取出待处理的时间段，可能是删除时段，也有可能是删除
        ArrayList<DoctorScheduleSlotVO> slots = (ArrayList<DoctorScheduleSlotVO>) param.get("slots");
//        存放添加的时间列表
        ArrayList<DoctorWorkPlanSchedule> addSlotList = new ArrayList<>();
//        存档删除的时间列表
        ArrayList<Integer> deleteSlots = new ArrayList<>();
//        筛选哪些时间段是删除，哪些时间段是添加
        slots.forEach(one->{
//            判断是添加时间段的条件
            if (one.getOperate().equals("insert")) {
                DoctorWorkPlanSchedule entity = new DoctorWorkPlanSchedule();
                entity.setWorkPlanId(workPlanId);
                entity.setMaximum(maximum);
                entity.setSlot(one.getSlot());
                addSlotList.add(entity);
            }else {
                deleteSlots.add(one.getScheduleId());  //保存到删除列表
            }
        });
//        处理删除列表
        if (deleteSlots.size()>0) {
//            查询这些待删除时间段的总挂号人数
            long sum = doctorWorkPlanScheduleDao.selectSumNumByIds(deleteSlots);
//            sum>0，说明有人挂号了，不能删
            if (sum>0) {
                throw new GlobalException("删除失败，本次修改与已经挂号记录冲突!");
            }
//            如果sum<0，那就可以删
            doctorWorkPlanScheduleDao.deleteByIds(deleteSlots);
//            删除redis中上面的这些时间段
            deleteSlots.forEach(one->{
                String key = DOCTOR_SCHEDULE_KEY + one;
                redisTemplate.delete(key);
            });
        }
//        处理添加列表
        addSlotList.forEach(one->{
            doctorWorkPlanScheduleDao.insert(one);
        });
        return addSlotList;
    }


    @Override
    @Transactional
    public void deleteWorkPlan(int workPlanId) {
        Integer number = doctorWorkPlanDao.selectNumById(workPlanId);
        if (number>0) {
            throw new GlobalException("该出诊计划已有患者挂号，禁止删除!");
        }
//        删除对应的时段
        doctorWorkPlanScheduleDao.deleteByWorkPlanId(workPlanId);
//        删除出诊计划
        doctorWorkPlanDao.deleteById(workPlanId);
    }

    @Override
    @Transactional
    public void deleteByWorkPlanId(int workPlanId) {
        ArrayList<HashMap> list = doctorWorkPlanScheduleDao.selectScheduleByWorkPlanId(workPlanId);
        //删除时段记录信息
        doctorWorkPlanScheduleDao.deleteByWorkPlanId(workPlanId);
        //删除redis缓存中的数据
        list.forEach(one -> {
            int scheduleId = MapUtil.getInt(one, "scheduleId");
            String key = DOCTOR_SCHEDULE_KEY + scheduleId;
            redisTemplate.delete(key);
        });
    }


    @Override
    public ArrayList<HashMap> selectDoctorWorkPlanSchedule(Map param) {
        return doctorWorkPlanScheduleDao.selectDoctorWorkPlanSchedule(param);
    }
}
